#Copyright (c) 2025, Alibaba Cloud and its affiliates;
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at

#   http://www.apache.org/licenses/LICENSE-2.0

#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.


import os
import shlex
import signal
import stat
import subprocess
from threading import Timer


def kill_all(parent_pid, sig=signal.SIGKILL):
    os.killpg(os.getpgid(parent_pid), sig)

def reset_sigchld(func):
    '''
    reset SIGCHLD handler before calling func, and recover after calling
    '''
    def reset_signal_call(*args, **kwargs):
        old_handler = signal.getsignal(signal.SIGCHLD)
        if old_handler != signal.SIG_DFL:
            signal.signal(signal.SIGCHLD, signal.SIG_DFL)
        retval = func(*args, **kwargs)
        if old_handler != signal.SIG_DFL:
            signal.signal(signal.SIGCHLD, old_handler)
        return retval
    return reset_signal_call

class Process():
    def __init__(self, cmd, shell=True, fd=None, env=None, signal_forward=True):
        self.proc_ = None
        self.ret_ = -1
        self.stdout_ = ''
        self.stderr_ = ''
        self.signal_forward_ = signal_forward
        try:
            if not shell:
                self.cmd_ = shlex.split(cmd.encode('ascii'))
            else:
                self.cmd_ = cmd
            if not signal_forward:
                preexec_fn_ = os.setsid
            else:
                preexec_fn_ = None
    
            ## merge stdout / stderr
            ## reasonable or not ??
            if not fd:
                self.proc_ = subprocess.Popen( self.cmd_, env=env, shell=shell,
                    preexec_fn=preexec_fn_, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
            else:
                self.proc_ = subprocess.Popen( self.cmd_, env=env, shell=shell,
                    preexec_fn=preexec_fn_, stdout=fd, stderr=fd )
        except Exception as e:
            self.stdout_ = str(e)
            if self.proc_:
                if self.signal_forward_:
                    os.kill(self.proc_.pid, signal.SIGKILL)
                else:
                    kill_all(self.proc_.pid, signal.SIGKILL)
                self.proc_ = None

    def communicate(self, timeout=0):
        if self.proc_:
            if timeout > 0:
                timer_ = Timer(timeout, kill_all, [self.proc_.pid])
                timer_.start()
                self.stdout_,self.stderr_ = self.proc_.communicate()
                timer_.cancel()
            else:
                self.stdout_,self.stderr_ = self.proc_.communicate()
            self.ret_ = int(self.proc_.returncode)
        return self.ret_, self.stdout_

    def kill(self, sig=signal.SIGKILL):
        'used by another thread to kill itself'
        if self.proc_:
            if self.signal_forward_:
                os.kill(self.proc_.pid, sig)
            else:
                kill_all(self.proc_.pid, sig)

@reset_sigchld
def RunWithout(cmd, timeout=0, shell=True, fd=None, env=None, signal_forward=False):
    'run external command with timeout limit'

    p = Process(cmd, shell, fd, env, signal_forward)
    return p.communicate(timeout)

@reset_sigchld
def RunWithoutRet(cmd, shell=True, fd=None, env=None, signal_forward=False):
    'run external command without communicate'

    p = Process(cmd, shell, fd, env, signal_forward)
    return

def run(cmd, timeout=0):
    status, output = RunWithout(cmd, timeout)
    return (status, output.decode())

def exec_script(script, *args):
    cur_dir = os.getcwd()
    # os.chdir(wrk_dir)
    if not os.path.exists(script):
        return (-1, "script %s is not exists" % script)
    
    args_str = " ".join(args)
    os.chmod(script, stat.S_IREAD + stat.S_IXUSR + stat.S_IXGRP + stat.S_IXOTH)
    cmd = "sh " + script + ' ' + args_str + " > %s.log 2>&1" % script
    return run(cmd)